/*
 * GravitationalForce.java
 *
 * Copyright 2008 the Desert Labs Project (http://desertlabs.us)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package us.desertlabs.physics;

import java.math.BigDecimal;
import us.desertlabs.number.BigUtility;

/**
 * This is used to represent an amount of gravitational force. Gravitational
 * force exists between any two objects that have mass.
 *
 * @author Brandon Franklin
 */
public class GravitationalForce extends Force {

    /**
     * Given a pair of <CODE>PhysicalObject</CODE>s, calculates and returns
     * the corresponding amount of <CODE>GravitationalForce</CODE> that is
     * experienced by the first object.
     *
     * @param obj1 the first <CODE>PhysicalObject</CODE>
     * @param obj2 the second <CODE>PhysicalObject</CODE>
     * @return the <CODE>GravitationalForce</CODE> experienced by the first
     *         object toward the second
     * @throws IllegalArgumentException if either of the parameters is invalid
     *         for any reason
     */
    static public GravitationalForce calculate( final PhysicalObject obj1, final PhysicalObject obj2 ) {

        // Validate the objects
        if( (obj1 == null) || (obj2 == null) ) {
            throw new IllegalArgumentException( "Objects cannot be null." );
        }

        // Multiply the masses of the objects
        BigDecimal mass1 = new BigDecimal( obj1.getMass().toString() );
        final BigDecimal mass2 = new BigDecimal( obj2.getMass().toString() );
        mass1 = BigUtility.normalizeScale( mass1.multiply( mass2 ) );

        // Square the distance between them
        final Distance dist = Distance.calculate( obj1.getLocation(), obj2.getLocation() );
        BigDecimal bigDecDist = new BigDecimal( dist.toString() );
        bigDecDist = BigUtility.clipScale( bigDecDist.multiply( bigDecDist ) );

        // Do the gravitational calculation
        final BigDecimal gravConst = new BigDecimal( obj1.getLocation().getSpace().getGravitationalConstant().toString() );
        BigDecimal gravForce = mass1.divide( bigDecDist, BigDecimal.ROUND_HALF_UP );
        gravForce = BigUtility.clipScale( gravForce.multiply( gravConst ) );

        // Break the distance between the objects into individual distance
        // components
        final Location obj1Loc = obj1.getLocation();
        final Location obj2Loc = obj2.getLocation();
        final BigDecimal[] distances = new BigDecimal[obj1Loc.getDimensionality()];
        final BigDecimal[] offsets = new BigDecimal[distances.length];
        final Number[] obj1Coords = obj1Loc.getCoordinates();
        final Number[] obj2Coords = obj2Loc.getCoordinates();
        for( int i = 0; i < distances.length; i++ ) {

            final BigDecimal obj1Coord = new BigDecimal( obj1Coords[i].toString() );
            final BigDecimal obj2Coord = new BigDecimal( obj2Coords[i].toString() );

            offsets[i] = obj1Coord.subtract( obj2Coord );
            distances[i] = offsets[i].multiply( offsets[i] );

            distances[i] = new BigDecimal( Double.toString( Math.sqrt( distances[i].doubleValue() ) ) );
        }

        // Use the distance components to parcel out the force value
        BigDecimal totalDistances = distances[0];
        for( int i = 1; i < distances.length; i++ ) {
            totalDistances = totalDistances.add( distances[i] );
        }

        final BigDecimal[] forceComps = new BigDecimal[distances.length];
        for( int i = 0; i < forceComps.length; i++ ) {
            distances[i] = BigUtility.normalizeScale( distances[i] );
            forceComps[i] = gravForce.multiply( distances[i].divide( totalDistances, BigDecimal.ROUND_HALF_UP ) );
            forceComps[i] = BigUtility.clipScale( forceComps[i] );
            if( offsets[i].signum() > 0 ) {
                forceComps[i] = forceComps[i].negate();
            }
        }

        return new GravitationalForce( forceComps );
    }

    /**
     * Creates a new instance of <CODE>GravitationalForce</CODE> with the
     * specified amounts of force in each dimension.
     *
     * @param values the amounts of force in each dimension
     * @throws IllegalArgumentException if the parameter is null or contains any
     *         null elements
     */
    public GravitationalForce( final Number[] values ) {
        super( values );
    }
}
